OPC Studio User's Guide and Reference
OPC UA Complex Data writing
Concepts > OPC Data Client Concepts > OPC Data Client Extensions > OPC Data Client Integrated Extensions > OPC UA Complex Data Extension > Basic OPC UA Complex Data operations > OPC UA Complex Data writing

To write a complex data information, prepare a value of the UAGenericObject Class, and call one of the Write methods on the EasyUAClient Class as usual.  In the UAGenericObject:

For discussion about creating the generic data, refer to Creating generic data. If the GenericData object you have created has a non-null DataType Property, the generic data will be first validated against this associated data type. For details on the validation process, refer to Generic data validation. The validation is optional, because the presence of the data type in the GenericData object is also optional. If no validation takes place, same errors may be discovered later during the encoding process. The validation has the advantage that it is done upfront, without a need to contact the server at all. It is therefore recommended to use it, if the data type is available somehow (see also "Resolving the data type" in Navigation in the OPC UA data model).

In some cases, you do not have to provide all data that are part of the data type. For example, it may be possible to omit the value for a field that determines a length of sequence in the same structure. For details, see Generic data auto-complete.

The example below first reads a structured value from the server, and then manipulates (changes) its elements. The modified value is then written to the server. In this case, the data type ID (and also the data type) can be taken over from what has been provided by the Read.

.NET

// Shows how to write complex data with OPC UA Complex Data plug-in.
//
// Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html .
// OPC client and subscriber examples in C# on GitHub: https://github.com/OPCLabs/Examples-QuickOPC-CSharp .
// Missing some example? Ask us for it on our Online Forums, https://www.opclabs.com/forum/index ! You do not have to own
// a commercial license in order to use Online Forums, and we reply to every post.

using System;
using OpcLabs.BaseLib.DataTypeModel;
using OpcLabs.EasyOpc.UA;
using OpcLabs.EasyOpc.UA.ComplexData;
using OpcLabs.EasyOpc.UA.OperationModel;

namespace UADocExamples.ComplexData._EasyUAClient
{
    class WriteValue
    {
        public static void Main1()
        {
            // Define which server and node we will work with.
            UAEndpointDescriptor endpointDescriptor =
                "opc.tcp://opcua.demo-this.com:51210/UA/SampleServer";
            // or "http://opcua.demo-this.com:51211/UA/SampleServer" (currently not supported)
            // or "https://opcua.demo-this.com:51212/UA/SampleServer/"
            UANodeDescriptor nodeDescriptor =
                "nsu=http://test.org/UA/Data/ ;i=10239"; // [ObjectsFolder]/Data.Static.Scalar.StructureValue

            // Instantiate the client object.
            var client = new EasyUAClient();

            // Read a node which returns complex data. 
            // We know that this node returns complex data, so we can type cast to UAGenericObject.
            Console.WriteLine("Reading...");
            UAGenericObject genericObject;
            try
            {
                genericObject = (UAGenericObject)client.ReadValue(endpointDescriptor, nodeDescriptor);
            }
            catch (UAException uaException)
            {
                Console.WriteLine("*** Failure: {0}", uaException.GetBaseException().Message);
                return;
            }


            // Modify the data read.
            // This node returns one of the two data types, randomly (this is not common, usually the type is fixed). The
            // data types are sub-types of one common type which the data type of the node. We therefore use the data type 
            // ID in the returned UAGenericObject to detect which data type has been returned.

            // For processing the internals of the data, refer to examples for GenericData and DataType classes.
            // We know how the data is structured, and have hard-coded a logic that modifies certain values inside. It is
            // also possible to discover the structure of the data type in the program, and write generic clients that can 
            // cope with any kind of complex data.
            //
            // Note that the code below is not fully robust - it will throw an exception if the data is not as expected.
            Console.WriteLine("Modifying...");
            Console.WriteLine(genericObject.DataTypeId);
            if (genericObject.DataTypeId.NodeDescriptor.Match("nsu=http://test.org/UA/Data/ ;i=9440"))  // ScalarValueDataType
            {
                // Negate the byte in the "ByteValue" field.
                var structuredData = (StructuredData)genericObject.GenericData;
                var byteValue = (PrimitiveData)structuredData.FieldData["ByteValue"];
                byteValue.Value = (Byte)~((Byte)byteValue.Value);
                Console.WriteLine(byteValue.Value);
            }
            else if (genericObject.DataTypeId.NodeDescriptor.Match("nsu=http://test.org/UA/Data/ ;i=9669")) // ArrayValueDataType
            {
                // Negate bytes at indexes 0 and 1 of the array in the "ByteValue" field.
                var structuredData = (StructuredData)genericObject.GenericData;
                var byteValue = (SequenceData)structuredData.FieldData["ByteValue"];
                var element0 = (PrimitiveData)byteValue.Elements[0];
                var element1 = (PrimitiveData)byteValue.Elements[1];
                element0.Value = (Byte)~((Byte)element0.Value);
                element1.Value = (Byte)~((Byte)element1.Value);
                Console.WriteLine(element0.Value);
                Console.WriteLine(element1.Value);
            }


            // Write the modified complex data back to the node.
            // The data type ID in the UAGenericObject is borrowed without change from what we have read, so that the server
            // knows which data type we are writing. The data type ID not necessary if writing precisely the same data type
            // as the node has (not a subtype).
            Console.WriteLine("Writing...");
            try
            {
                client.WriteValue(endpointDescriptor, nodeDescriptor, genericObject);
            }
            catch (UAException uaException)
            {
                Console.WriteLine("*** Failure: {0}", uaException.GetBaseException().Message);
            }
        }
    }
}
' Shows how to write complex data with OPC UA Complex Data plug-in.
'
' Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html .
' OPC client and subscriber examples in VB.NET on GitHub: https://github.com/OPCLabs/Examples-QuickOPC-VBNET .
' Missing some example? Ask us for it on our Online Forums, https://www.opclabs.com/forum/index ! You do not have to own
' a commercial license in order to use Online Forums, and we reply to every post.

Imports System
Imports OpcLabs.BaseLib.DataTypeModel
Imports OpcLabs.EasyOpc.UA
Imports OpcLabs.EasyOpc.UA.ComplexData
Imports OpcLabs.EasyOpc.UA.OperationModel

Namespace ComplexData._EasyUAClient

    Friend Class WriteValue

        Public Shared Sub Main1()

            ' Define which server we will work with.
            Dim endpointDescriptor As UAEndpointDescriptor =
                    "opc.tcp://opcua.demo-this.com:51210/UA/SampleServer"
            ' or "http://opcua.demo-this.com:51211/UA/SampleServer" (currently not supported)
            ' or "https://opcua.demo-this.com:51212/UA/SampleServer/"

            ' Define which node we will work with.
            Dim nodeDescriptor As UANodeDescriptor = _
                "nsu=http://test.org/UA/Data/ ;i=10239"  ' [ObjectsFolder]/Data.Static.Scalar.StructureValue

            ' Instantiate the client object.
            Dim client = New EasyUAClient

            ' Read a node which returns complex data. 
            ' We know that this node returns complex data, so we can type cast to UAGenericObject.
            Console.WriteLine("Reading...")
            Dim genericObject As UAGenericObject
            Try
                genericObject = CType(client.ReadValue(endpointDescriptor, nodeDescriptor), UAGenericObject)
            Catch uaException As UAException
                Console.WriteLine("*** Failure: {0}", uaException.GetBaseException.Message)
                Exit Sub
            End Try


            ' Modify the data read.
            ' This node returns one of the two data types, randomly (this is not common, usually the type is fixed). The
            ' data types are sub-types of one common type which the data type of the node. We therefore use the data type 
            ' ID in the returned UAGenericObject to detect which data type has been returned.
            ' For processing the internals of the data, refer to examples for GenericData and DataType classes.
            ' We know how the data is structured, and have hard-coded a logic that modifies certain values inside. It is
            ' also possible to discover the structure of the data type in the program, and write generic clients that can 
            ' cope with any kind of complex data.
            '
            ' Note that the code below is not fully robust - it will throw an exception if the data is not as expected.
            Console.WriteLine("Modifying...")
            Console.WriteLine(genericObject.DataTypeId)
            If genericObject.DataTypeId.NodeDescriptor.Match("nsu=http://test.org/UA/Data/ ;i=9440") Then    ' ScalarValueDataType
                ' Negate the byte in the "ByteValue" field.
                Dim structuredData = CType(genericObject.GenericData, StructuredData)
                Dim byteValue = CType(structuredData.FieldData("ByteValue"), PrimitiveData)
                byteValue.Value = CType(Not CType(byteValue.Value, Byte), Byte)
                Console.WriteLine(byteValue.Value)
            ElseIf genericObject.DataTypeId.NodeDescriptor.Match("nsu=http://test.org/UA/Data/ ;i=9669") Then    ' ArrayValueDataType
                ' Negate bytes at indexes 0 and 1 of the array in the "ByteValue" field.
                Dim structuredData = CType(genericObject.GenericData, StructuredData)
                Dim byteValue = CType(structuredData.FieldData("ByteValue"), SequenceData)
                Dim element0 = CType(byteValue.Elements(0), PrimitiveData)
                Dim element1 = CType(byteValue.Elements(1), PrimitiveData)
                element0.Value = CType(Not CType(element0.Value, Byte), Byte)
                element1.Value = CType(Not CType(element1.Value, Byte), Byte)
                Console.WriteLine(element0.Value)
                Console.WriteLine(element1.Value)
            End If


            ' Write the modified complex data back to the node.
            ' The data type ID in the UAGenericObject is borrowed without change from what we have read, so that the server
            ' knows which data type we are writing. The data type ID not necessary if writing precisely the same data type
            ' as the node has (not a subtype).
            Console.WriteLine("Writing...")
            Try
                client.WriteValue(endpointDescriptor, nodeDescriptor, genericObject)
            Catch uaException As UAException
                Console.WriteLine("*** Failure: {0}", uaException.GetBaseException.Message)
                Exit Sub
            End Try
        End Sub
    End Class
End Namespace

COM

// Shows how to write complex data with OPC UA Complex Data plug-in.
//
// Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html .
// OPC client and subscriber examples in Object Pascal (Delphi) on GitHub: https://github.com/OPCLabs/Examples-QuickOPC-OP .
// Missing some example? Ask us for it on our Online Forums, https://www.opclabs.com/forum/index ! You do not have to own
// a commercial license in order to use Online Forums, and we reply to every post.

class procedure WriteValue.Main;
var
  ArrayValueDataType: _UANodeDescriptor;
  ByteValue: _PrimitiveData;
  ByteValue2: _SequenceData;
  Client: _EasyUAClient;
  Element0, Element1: _PrimitiveData;
  EndpointDescriptor: string;
  GenericObject: _UAGenericObject;
  NodeDescriptor: string;
  ScalarValueDataType: _UANodeDescriptor;
  StructuredData: _StructuredData;
begin
  // Define which server and node we will work with.
  EndpointDescriptor := 
    //'http://opcua.demo-this.com:51211/UA/SampleServer';
    //'https://opcua.demo-this.com:51212/UA/SampleServer/';
    'opc.tcp://opcua.demo-this.com:51210/UA/SampleServer';
  NodeDescriptor := 'nsu=http://test.org/UA/Data/ ;i=10239';  // [ObjectsFolder]/Data.Static.Scalar.StructureValue

  // Instantiate the client object
  Client := CoEasyUAClient.Create;

  // Read a node which returns complex data.
  // We know that this node returns complex data, so we can type cast to UAGenericObject.
  WriteLn('Reading...');

  try
    GenericObject := _UAGenericObject(IUnknown(Client.ReadValue(EndpointDescriptor, NodeDescriptor)));
  except
    on E: EOleException do
    begin
      WriteLn(Format('*** Failure: %s', [E.GetBaseException.Message]));
      Exit;
    end;
  end;

  // Modify the data read.
  // This node returns one of the two data types, randomly (this is not common, usually the type is fixed). The
  // data types are sub-types of one common type which the data type of the node. We therefore use the data type
  // ID in the returned UAGenericObject to detect which data type has been returned.

  // For processing the internals of the data, refer to examples for GenericData and DataType classes.
  // We know how the data is structured, and have hard-coded a logic that modifies certain values inside. It is
  // also possible to discover the structure of the data type in the program, and write generic clients that can
  // cope with any kind of complex data.
  //
  // Note that the code below is not fully robust - it will throw an exception if the data is not as expected.

  WriteLn('Modifying...');
  WriteLn(GenericObject.DataTypeId.ToString);
  ScalarValueDataType := CoUANodeDescriptor.Create;
  ScalarValueDataType.NodeId.ExpandedText := 'nsu=http://test.org/UA/Data/ ;i=9440'; // ScalarValueDataType
  if GenericObject.DataTypeId.NodeDescriptor.Match(ScalarValueDataType) then
  begin
    // Negate the byte in the "ByteValue" field.
    StructuredData := IUnknown(GenericObject.GenericData) as _StructuredData;
    ByteValue := IUnknown(StructuredData.FieldData['ByteValue']) as _PrimitiveData;
    ByteValue.Value := Byte(not (Byte(byteValue.Value)));
    WriteLn(ByteValue.Value);
  end
  else
  begin
    ArrayValueDataType := CoUANodeDescriptor.Create;
    ArrayValueDataType.NodeId.ExpandedText := 'nsu=http://test.org/UA/Data/ ;i=9669'; // ArrayValueDataType
    if GenericObject.DataTypeId.Nodedescriptor.Match(ArrayValueDataType) then
    begin
      // Negate bytes at indexes 0 and 1 of the array in the "ByteValue" field.
      StructuredData := IUnknown(GenericObject.GenericData) as _StructuredData;
      ByteValue2 := IUnknown(StructuredData.FieldData['ByteValue']) as _SequenceData;
      Element0 := IUnknown(ByteValue2.Elements[0]) as _PrimitiveData;
      Element1 := IUnknown(ByteValue2.Elements[1]) as _PrimitiveData;
      Element0.Value := Byte(not (Byte(element0.Value)));
      Element1.Value := Byte(not (Byte(element1.Value)));
      WriteLn(Element0.Value);
      WriteLn(Element1.Value);
    end;
  end;

  // Write the modified complex data back to the node.
  // The data type ID in the UAGenericObject is borrowed without change from what we have read, so that the server
  // knows which data type we are writing. The data type ID not necessary if writing precisely the same data type
  // as the node has (not a subtype).
  WriteLn('Writing...');
  try
    Client.WriteValue(EndpointDescriptor, NodeDescriptor, GenericObject);
  except
    on E: EOleException do
    begin
      WriteLn(Format('*** Failure: %s', [E.GetBaseException.Message]));
      Exit;
    end;
  end;

end;
// Shows how to write complex data with OPC UA Complex Data plug-in.
//
// Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html .
// OPC client and subscriber examples in PHP on GitHub: https://github.com/OPCLabs/Examples-QuickOPC-PHP .
// Missing some example? Ask us for it on our Online Forums, https://www.opclabs.com/forum/index ! You do not have to own
// a commercial license in order to use Online Forums, and we reply to every post.

// Define which server and node we will work with.
$EndpointDescriptor = 
    //"http://opcua.demo-this.com:51211/UA/SampleServer";
    //"https://opcua.demo-this.com:51212/UA/SampleServer/";
    "opc.tcp://opcua.demo-this.com:51210/UA/SampleServer";
$NodeDescriptor = "nsu=http://test.org/UA/Data/ ;i=10239";  // [ObjectsFolder]/Data.Static.Scalar.StructureValue

// Instantiate the client object
$Client = new COM("OpcLabs.EasyOpc.UA.EasyUAClient");

// Read a node which returns complex data.
// We know that this node returns complex data, so we can type cast to UAGenericObject.
printf("Reading...\n");

try
{
    $GenericObject = $Client->ReadValue($EndpointDescriptor, $NodeDescriptor);
}
catch (com_exception $e)
{
    printf("*** Failure: %s\n", $e->getMessage());
    Exit();
}

// Modify the data read.
// This node returns one of the two data types, randomly (this is not common, usually the type is fixed). The
// data types are sub-types of one common type which the data type of the node. We therefore use the data type
// ID in the returned UAGenericObject to detect which data type has been returned.

// For processing the internals of the data, refer to examples for GenericData and DataType classes.
// We know how the data is structured, and have hard-coded a logic that modifies certain values inside. It is
// also possible to discover the structure of the data type in the program, and write generic clients that can
// cope with any kind of complex data.
//
// Note that the code below is not fully robust - it will throw an exception if the data is not as expected.

printf("Modifying...\n");
printf("%s\n", $GenericObject->DataTypeId);
$ScalarValueDataType = new COM("OpcLabs.EasyOpc.UA.UANodeDescriptor");
$ScalarValueDataType->NodeId->ExpandedText = "nsu=http://test.org/UA/Data/ ;i=9440"; // ScalarValueDataType
if ($GenericObject->DataTypeId->NodeDescriptor->Match($ScalarValueDataType)) {
    // Negate the byte in the "ByteValue" field.
    $StructuredData = $GenericObject->GenericData->AsStructuredData();
    $ByteValue = $StructuredData->FieldData["ByteValue"]->AsPrimitiveData();
    $ByteValue->Value = ~($ByteValue->Value) & 255;
    printf("%s\n", $ByteValue->Value);
}
else {
    $ArrayValueDataType = new COM("OpcLabs.EasyOpc.UA.UANodeDescriptor");
    $ArrayValueDataType->NodeId->ExpandedText = "nsu=http://test.org/UA/Data/ ;i=9669"; // ArrayValueDataType
    if ($GenericObject->DataTypeId->Nodedescriptor->Match($ArrayValueDataType)) {
        // Negate bytes at indexes 0 and 1 of the array in the "ByteValue" field.
        $StructuredData = $GenericObject->GenericData->AsStructuredData();
        $ByteValue2 = $StructuredData->FieldData["ByteValue"]->AsSequenceData();
        $Element0 = $ByteValue2->Elements[0]->AsPrimitiveData();
        $Element1 = $ByteValue2->Elements[1]->AsPrimitiveData();
        $Element0->Value = ~($Element0->Value) & 255;
        $Element1->Value = ~($Element1->Value) & 255;
        printf("%s\n", $Element0->Value);
        printf("%s\n", $Element1->Value);
    }
}

// Write the modified complex data back to the node.
// The data type ID in the UAGenericObject is borrowed without change from what we have read, so that the server
// knows which data type we are writing. The data type ID not necessary if writing precisely the same data type
// as the node has (not a subtype).
printf("Writing...\n");
try
{
    $Client->WriteValue($EndpointDescriptor, $NodeDescriptor, $GenericObject);
}
catch (com_exception $e)
{
    printf("Failure: %s\n", $e->getMessage());
    Exit();
}

Python

# Shows how to write complex data with OPC UA Complex Data plug-in.
#
# Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html .
# OPC client and subscriber examples in Python on GitHub: https://github.com/OPCLabs/Examples-QuickOPC-Python .
# Missing some example? Ask us for it on our Online Forums, https://www.opclabs.com/forum/index ! You do not have to own
# a commercial license in order to use Online Forums, and we reply to every post.
# The QuickOPC package is needed. Install it using "pip install opclabs_quickopc".
import opclabs_quickopc

# Import .NET namespaces.
from System import *
from OpcLabs.BaseLib.DataTypeModel import *
from OpcLabs.EasyOpc.UA import *
from OpcLabs.EasyOpc.UA.OperationModel import *


endpointDescriptor = UAEndpointDescriptor('opc.tcp://opcua.demo-this.com:51210/UA/SampleServer')
# or 'http://opcua.demo-this.com:51211/UA/SampleServer' (currently not supported)
# or 'https://opcua.demo-this.com:51212/UA/SampleServer/'

# [ObjectsFolder]/Data.Static.Scalar.StructureValue
nodeDescriptor = UANodeDescriptor('nsu=http://test.org/UA/Data/ ;i=10239')

# Instantiate the client object.
client = EasyUAClient()

# Read a node which returns complex data.
# We know that this node returns complex data, so we can type cast to UAGenericObject.
try:
    print('Reading...')
    genericObject = IEasyUAClientExtension.ReadValue(client, endpointDescriptor, nodeDescriptor)
except UAException as uaException:
    print('*** Failure: ' + uaException.GetBaseException().Message)
    exit()

# Modify the data read.
# This node returns one of the two data types, randomly (this is not common, usually the type is fixed). The
# data types are sub-types of one common type which the data type of the node. We therefore use the data type
# ID in the returned UAGenericObject to detect which data type has been returned.
#
# For processing the internals of the data, refer to examples for GenericData and DataType classes.
# We know how the data is structured, and have hard-coded a logic that modifies certain values inside. It is
# also possible to discover the structure of the data type in the program, and write generic clients that can
# cope with any kind of complex data.
#
# Note that the code below is not fully robust - it will throw an exception if the data is not as expected.
print ('Modifying...')
print(genericObject.DataTypeId)
if genericObject.DataTypeId.NodeDescriptor.Match(UANodeDescriptor('nsu=http://test.org/UA/Data/ ;i=9440')):  # ScalarValueDataType
    # Negate the byte in the "ByteValue" field.
    structuredData = genericObject.GenericData
    byteValue = structuredData.FieldData.get_Item('ByteValue')  # PrimitiveData
    byteValue.Value = (~byteValue.Value) & 0xFF
    print(byteValue.Value)
elif genericObject.DataTypeId.NodeDescriptor.Match(UANodeDescriptor('nsu=http://test.org/UA/Data/ ;i=9669')): # ArrayValueDataType
    # Negate bytes at indexes 0 and 1 of the array in the "ByteValue" field.
    structuredData = genericObject.GenericData
    byteValue = structuredData.FieldData.get_Item('ByteValue')  # SequenceData
    element0 = byteValue.Elements.get_Item(0)    # PrimitiveData
    element1 = byteValue.Elements.get_Item(1)    # PrimitiveData
    element0.Value = (~element0.Value) & 0xFF
    element1.Value = (~element1.Value) & 0xFF
    print(element0.Value)
    print(element1.Value)


# Write the modified complex data back to the node.
# The data type ID in the UAGenericObject is borrowed without change from what we have read, so that the server
# knows which data type we are writing. The data type ID not necessary if writing precisely the same data type
# as the node has (not a subtype).
try:
    print('Writing...')
    IEasyUAClientExtension.WriteValue(client, endpointDescriptor, nodeDescriptor, genericObject)
except UAException as uaException:
    print('*** Failure: ' + uaException.GetBaseException().Message)


print()
print('Finished.')

 

See Also

Recommended

Reference